/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;

import org.javaongems.client.rt.io.AsyncCallbackFilter;
import org.javaongems.client.service.ResourceBundleRpc.LocaleInfo;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.HistoryListener;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowCloseListener;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

/**
 * <p>Represents the container for the entire client-side gem solution.</p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> provide a gwt onload module entry point
 *  <li> load the gemlet specified in the entry points (Example.gwt.xml) descriptor
 *  <li> load the containers listeners (ie. window and history management)
 *  <li> coordinate container management events
 *  <li> delegate application specifics to gemlet 
 * </ul>
 * </p>
 * <p>Collaborators:
 * <ul>
 * 	<li> Browser - window closer, direct url manipulation, back/forward navigation 
 * 	<li> Coordinator - primary specification for the Gem container
 * 	<li> Controller - recipients of broadcasted container events
 *  <li> Command - to realise undo/redo actions
 * </ul>
 * </p>
 * <p>Constraints:
 * <ul>
 * 	<li> GWT emulation subsystem
 * 	<li> GWT compiler does not support Java generics
 * </ul>
 * </p>
 * 
 * @author Ashin Wimalajeewa (ash)
 */
public class Gem implements 
							Coordinator, 
							EntryPoint,				// gwt bootstrap 
							HistoryListener, 		// gwt history
							WindowCloseListener 	// window management
							{
	/**
	 * Provides access to the real co-ordinator implementation. This is object 
	 * that is referenced throughout Gem bases solution to make requests of the
	 * container.
	 */
	static public Coordinator stone;
	
	final static public String GEM_HISTORY = "gem.hist-";
	final static public int GEM_HIST_LEN = GEM_HISTORY.length();
	
	protected Gemlet 	delegate;
	protected int 		pageIdx = 0;
	protected Stack 	backState = new Stack();
	protected Stack 	forwardState = new Stack();
	
	protected Stack controllers = new Stack();
	private HashMap	cargo = new HashMap();

	/**
	 * Command entry point used to load a load a Gems application. Note, all Gems
	 * applications use this same entry point in their application's Main gwt.xml
	 * descriptor. This implementation initialises the Gemlet, initialises 
	 * container listeners, instructs the Gemlet to initialise its view and 
	 * instructs the Gemlet to handles page load errors if any.  
	 * <pre>
	 * 	&lt;entry-point class='org.javaongems.client.Gem'/&gt;
	 * </pre>
	 * 
	 * @see com.google.gwt.core.client.EntryPoint#onModuleLoad()
	 * @see #initialiseListeners()
	 * @see Gemlet#initialiseView()
	 * @see Gemlet#handlePageLoadFailureIfAny()
	 */
	public void onModuleLoad() {
		stone = this;
		initialiseGemlet();
		initialiseListeners();
		initialiseView();
		handlePageLoadFailureIfAny();
	}
	
	/**
	 * Event handler that is invoked by the browser when Commands are 
	 * transitioned to or when the browser navigates forward or backward. This
	 * implementation will invoke the appropriates Command undo() or redo() 
	 * method if a transition is required.
	 * 
	 * @param historyToken - is a history token that is of the form: "gem.hist-xx". 
	 * This suffix is incremented when new commands are transitioned to using the
	 * onTransition() method.
	 * 
	 * @see #back()
	 * @see #forward()
	 * @see #getNavigationRequest(String, int[])
	 * @see com.google.gwt.user.client.HistoryListener#onHistoryChanged(java.lang.String)
	 */
	public void onHistoryChanged(String historyToken) {
		if (historyToken == null || !historyToken.startsWith(GEM_HISTORY))
			return;
		int[] pos = new int[1];
		int action = getNavigationRequest(historyToken, pos);
		if (action == 0)
			return;
		if (action == 1)
			forward();
		else
			back();
		pageIdx = pos[0];
	}

	/**
	 * Event handler that is invoked by the browser when either:
	 * 1. the browser' page is attempted to be closed, or 2. the user attempts 
	 * to navigate away from the current page using direct url manipulation or 
	 * a favourite bookmark. This implementation will traverse all controllers
	 * and give each the opportunity to veto the closure.
	 * 
	 * @see com.google.gwt.user.client.WindowCloseListener#onWindowClosing()
	 * @see Controller#containerCloseRequest()
	 */
	public String onWindowClosing() {
		for (Iterator iter = controllers.iterator(); iter.hasNext();) {
			Controller ctrlr = (Controller) iter.next();
			String msg = ctrlr.containerCloseRequest();
			if (msg != null)
				return msg;
		}
		return null;
	}

	/**
	 * Event handler that is invoked by the browser after all currently
	 * opened controllers have OK'd the closure. This implementation will
	 * traverse all controllers and instruct each to tearDown.  
	 * 
	 * @see com.google.gwt.user.client.WindowCloseListener#onWindowClosed()
	 * @see Controller#tearDown()
	 */
	public void onWindowClosed() {
		ArrayList tmp = new ArrayList(controllers);
		for (Iterator iter = tmp.iterator(); iter.hasNext();) {
			Controller ctrlr = (Controller) iter.next();
			ctrlr.tearDown();
		}
	}
		
	// Co-ordinator interface
	/**
	 * Command that transitions that state of the Gem application back one
	 * Command (ie. the previous executed Commands undo() method).
	 * 
	 * @see org.javaongems.client.Coordinator#back()
	 * @see Command#undo()
	 */
	public void back() {
		if (backState.size() < 2)
			return;
		Command cmd = (Command) backState.pop();
		forwardState.push(cmd);
		cmd.undo();
	}

	/**
	 * Command that transitions the state of the Gem application forward one
	 * Command (ie. the next execute Commands redo() method).
	 * 
	 * @see org.javaongems.client.Coordinator#forward()
	 * @see Command#redo()
	 */
	public void forward() {
		if (forwardState.isEmpty())
			return;
		Command cmd = (Command) forwardState.pop();
		backState.push(cmd);
		cmd.redo();
	}

	/**
	 * Command that logs a message to the logging device associated with the
	 * Gemlet.
	 * 
	 * @see org.javaongems.client.Coordinator#log(java.lang.String)
	 * @see #log(String, Throwable) - with null throwable value
	 */
	public void log(String msg) {
		log(msg, null);
	}
	
	/**
	 * Command that is invoked by controllers during their setup. Commands 
	 * register themselves using the open controller method so that they can 
	 * participate in container events.
	 * 
	 * @see org.javaongems.client.Coordinator#openController(org.javaongems.client.Controller)
	 * @see Controller#setUp(Map)
	 */
	public void openController(Controller toBringToFront) {
		int pos = controllers.indexOf(toBringToFront);
		if (pos > -1)
			controllers.remove(pos);
		controllers.push(toBringToFront);
	}
	
	/**
	 * Command that is invoked by controllers during their tearDown. Commands 
	 * register themselves using the open controller method so that they can 
	 * participate in container events. If this close is not found then they
	 * will remain subscribed to those events.
	 * 
	 * @see org.javaongems.client.Coordinator#closeController(org.javaongems.client.Controller)
	 * @see Controller#tearDown()
	 */
	public void closeController(Controller toClose) {
		controllers.remove(toClose);
	}

	/**
	 * Query the cargo associated with the one and only Gem. Remember, that there is
	 * only one coordinator which is represented by the "stone" variable. This
	 * means that this is globally accessible cargo. 
	 * @return the cargo (user-defined baggage) that is pinned to the Gem
	 * @see org.javaongems.client.Coordinator#getCargo()
	 */
	public Map getCargo() {
		return cargo;
	}
	
	/**
	 * Query that delegates the retrieval of the gem property to the Gemlet and then
	 * either returns the stringified value or the default value if the returned value
	 * was null.
	 * 
	 * @see org.javaongems.client.Coordinator#getGemPropertyAsString(java.lang.String, java.lang.String)
	 * @see Gemlet#getGemProperty(String)
	 */
	public String getGemPropertyAsString(String prop, String defVal) {
		Object val = getGemProperty(prop);
		if (val instanceof String)	
			val = val.toString();
		if (val == null)
			return defVal;
		return "" + val;
	}

	/**
	 * Command that modifies the back and forward state machines of the Gem 
	 * application. Whenever a command that is undoable is executed in the 
	 * Gem application, the onTransition() method should be invoked. This 
	 * notification will update the forward and backward stacks and also
	 * create a new history item using the next history reference.
	 * 
	 * @see org.javaongems.client.Coordinator#onTransition(org.javaongems.client.Command)
	 * @see History#newItem(String)
	 * @see #createNextHistoryReference()
	 */
	public void	onTransition(Command cmd) {
		backState.push(cmd);
		forwardState.clear();
		History.newItem(createNextHistoryReference());
	}
	
	/**
	 * <p>Command that changes the page context in the browser window. Such a change
	 * will cause container events to fire.<p>
	 * 
	 * <p><b>Important</b>: The current GWT History implementation looses all 
	 * transitions and the Gemlets state is lost when urls are changed. Therefore, 
	 * Gemlet application developers must not infer that the history contract 
	 * crosses page context boundaries. 
	 * </p>
	 * 
	 * @see org.javaongems.client.Coordinator#openUrl(java.lang.String)
	 * @see #onWindowClosing()
	 * @see #onWindowClosed()
	 */
	native public void openUrl(String url) /*-{
		$wnd.location.href = url; 
	}-*/;

	/**
	 * Command that is responsible for constructing the actual Gemlet that is
	 * used in the Gem application. Although a templated Gemlet appears to be
	 * constructed in the implementation, the real Gemlet will be replaced
	 * using the GWT module replace tags.
	 * <pre>
	 *	&lt;replace-with class="au.com.gworks.jump.app.wiki.client.Jump"&gt;
	 *		&lt;when-type-is class="org.javaongems.client.TemplateGemlet"/&gt;
	 *	&lt;/replace-with&gt;
	 * </pre> 
	 */
	protected void initialiseGemlet() {
		delegate = (Gemlet) GWT.create(TemplateGemlet.class);
	}
	
	/**
	 * Query that returns the blank image that is shipped in the public images 
	 * directory of the gems distribution.
	 * @return getApplImageCtx() + "blank.gif"  
	 * @see org.javaongems.client.Coordinator#getBlankImageUri()
	 */
	public String getBlankImageUri() {
		return getApplImageCtx() + "blank.gif";
	}

	/**
	 * Query that returns the images folder that is shipped in the public 
	 * directory of the gems distributions. All Gem applications are 
	 * encourage to use this same public directory for consistency.
	 * @return getApplCtx() + "images/"  
	 * @see org.javaongems.client.Coordinator#getApplImageCtx()
	 */
	public String getApplImageCtx() {
		return getApplCtx() + "images/";
	}

	/**
	 * Command that adds this Gem object as a listener to window close events
	 * and history events.
	 * 
	 * @see Window#addWindowCloseListener(WindowCloseListener)
	 * @see History#addHistoryListener(HistoryListener)
	 */
	protected void initialiseListeners() {
		Window.addWindowCloseListener(this);
		History.addHistoryListener(this);
	}
	
	/**
	 * Query that inspects the history token and determines the next action 
	 * based on sequencing.
	 * 
	 * @param token - the history token issued by the browser upon back/forward events
	 * @param store - a store for the history sequence number
	 * @return 0 - for no transition, 1 - to go back, 2 - to go forward 
	 */
	protected int getNavigationRequest(String token, int[] store) {
		Integer val = new Integer(token.substring(GEM_HIST_LEN));
		store[0] = val.intValue();
		if (store[0] == pageIdx)
			return 0;
		if (store[0] > pageIdx)
			return 1;
		return -1;
	}
	
	/**
	 * Command that increments the page index used for constructing history 
	 * tokens.
	 * @see #GEM_HISTORY
	 * @return GEM_HISTORY + ++pageIdx;
	 */
	protected String createNextHistoryReference() {
		++pageIdx;
		return GEM_HISTORY + pageIdx;
	}
	
	// Gemlet delegation
	/**
	 * @see org.javaongems.client.Gemlet#getApplCtx()
	 */
	public String getApplCtx() {
		return delegate.getApplCtx();
	}

	/**
	 * @see org.javaongems.client.Gemlet#getGemProperty(java.lang.String)
	 */
	public Object getGemProperty(String prop) {
		return delegate.getGemProperty(prop);
	}

	/**
	 * @see org.javaongems.client.Gemlet#getResourceBundle(java.lang.String, org.javaongems.client.service.ResourceBundleRpc.LocaleInfo)
	 */
	public Map getResourceBundle(String baseName, LocaleInfo locale) {
		return delegate.getResourceBundle(baseName, locale);
	}
	
	/**
	 * @see org.javaongems.client.Gemlet#decorateCallback(com.google.gwt.user.client.rpc.AsyncCallback, java.lang.Object[])
	 */
	public AsyncCallbackFilter decorateCallback(AsyncCallback toDecorate, Object[] args) {
		return delegate.decorateCallback(toDecorate, args);
	}
	
	/**
	 * @see org.javaongems.client.Gemlet#openParameterisedUrl(java.lang.String[])
	 */
	public void openParameterisedUrl(String[] args) {
		delegate.openParameterisedUrl(args);
	}
	
	/**
	 * @see org.javaongems.client.Gemlet#isDebugEnabled()
	 */
	public boolean isDebugEnabled() {
		return delegate.isDebugEnabled();
	}

	/**
	 * @see org.javaongems.client.Gemlet#log(java.lang.String, java.lang.Throwable)
	 */
	public void log(String msg, Throwable err) {
		delegate.log(msg, err);
	}

	/**
	 * @see org.javaongems.client.Gemlet#reportStatus(java.lang.String)
	 */
	public void reportStatus(String msg) {
		delegate.reportStatus(msg);
	}

	/**
	 * @see org.javaongems.client.Gemlet#alertMessage(java.lang.String)
	 */
	public void alertMessage(String msg) {
		delegate.alertMessage(msg);
	}

	/**
	 * @see org.javaongems.client.Gemlet#handleServiceFailures(java.lang.Throwable)
	 */
	public void handleServiceFailures(Throwable caught) {
		delegate.handleServiceFailures(caught);
	}

	/**
	 * @see org.javaongems.client.Gemlet#handlePageLoadFailureIfAny()
	 */
	public void handlePageLoadFailureIfAny() {
		delegate.handlePageLoadFailureIfAny();
	}

	/**
	 * @see org.javaongems.client.Gemlet#openService(java.lang.String)
	 */
	public ServiceDefTarget openService(String serviceName) {
		return delegate.openService(serviceName);
	}

	/**
	 * @see org.javaongems.client.Gemlet#initialiseView()
	 */
	public void initialiseView() {
		delegate.initialiseView();
	}

	/**
	 * @see org.javaongems.client.Gemlet#getGemId()
	 */
	public String getGemId() {
		return delegate.getGemId();
	}

	/**
	 * @see org.javaongems.client.Coordinator#getEndpointForService(java.lang.String)
	 */
	public String getEndpointForService(String serviceName) {
		return delegate.getEndpointForService(serviceName);
	}
}
